package jnpf.database.util;

import jnpf.database.model.dto.DataSourceDTO;
import jnpf.database.model.interfaces.DataSourceMod;
import jnpf.database.source.DbBase;
import jnpf.database.source.impl.DbOracle;
import jnpf.exception.DataException;
import java.sql.*;
import java.util.Properties;
import java.util.concurrent.*;

/**
 * Connection数据连接工具类
 *
 * @author JNPF开发平台组
 * @version V3.2.0
 * @copyright 引迈信息技术有限公司
 * @date 2021/10/6
 */
public class ConnUtil {

    /**
     * 连接Connection
     */
    public static Connection getConn(DataSourceMod dbSourceOrDbLink) throws DataException {
        return getConn(dbSourceOrDbLink,null);
    }

    /**
     * 指定库名（多租户）
     * @param dataSourceUtil 配置数据源信息
     * @param dbName 数据库名
     * @return url连接
     */
    public static Connection getConn(DataSourceMod dataSourceUtil, String dbName) throws DataException {
        DataSourceDTO dataSourceDTO = dataSourceUtil.dataSourceDTO();
        DbBase dbBase = DbTypeUtil.getDb(dataSourceUtil);
        //Oracle特殊添加数据连接方式
        if(dbBase.getClass() == DbOracle.class){
            DataSourceDTO dsd = dataSourceDTO.reDbName(dbName);
            return createOracleConn(dbBase.getDriver(), dataSourceDTO.getUserName(), dataSourceDTO.getPassword(),
                    dbBase.getConnUrl(dsd.getPrepareUrl(),dsd.getHost(),dsd.getPort(),dsd.getDbName()));
        }else {
            DataSourceDTO dsd = dataSourceDTO.reDbName(dbName);
            return createConn(dbBase.getDriver(), dataSourceDTO.getUserName(), dataSourceDTO.getPassword(),
                    dbBase.getConnUrl(dsd.getPrepareUrl(),dsd.getHost(),dsd.getPort(),dsd.getDbName()));
        }
    }

    public static Connection getConn(String userName, String password, String url) throws Exception {
        return createConn(DbTypeUtil.getDb(url).getDriver(), userName, password, url);
    }

    /**
     * 开启数据库获取表注解连接
     * @param dbSourceOrDbLink 数据源对象
     * @return ignore
     */
    public static Connection getConnRemarks(DataSourceUtil dbSourceOrDbLink) throws DataException {
        Properties props = new Properties();
        props.setProperty("remarks", "true");
        props.setProperty("useInformationSchema","true");
        props.setProperty("remarksReporting", "true");
        return createConnByProp(dbSourceOrDbLink.getDriver(), dbSourceOrDbLink.getUserName(), dbSourceOrDbLink.getPassword(),
                DbTypeUtil.getUrl(dbSourceOrDbLink), props);
    }

    /**
     * （基础）获取数据连接
     * @param driver 驱动
     * @param userName 用户
     * @param password 密码
     * @param url url
     * @return 数据库连接
     * @throws DataException ignore
     */
    private static Connection createConn(String driver, String userName, String password, String url) throws DataException {
        try{
            Class.forName(driver);
            return DriverManager.getConnection(url, userName, password);
        }catch (Exception e){
            throw new DataException(e.getMessage());
        }
    }

    private static Connection createOracleConn(String driver, String userName, String password, String url) throws DataException {
        //Oracle登录角色设置（Default，SYSDBA，SYSOPER）
        Properties conProps = DbOracle.setConnProp(userName, password);
        return createConnByProp(driver, conProps.getProperty("user"), password, url, conProps);
    }

    private static Connection createConnByProp(String driver, String userName, String password, String url, Properties conProps) throws DataException {
        try{
            conProps.put("user", userName);
            conProps.put("password", password);
            Class.forName(driver);
            return DriverManager.getConnection(url, conProps);
        }catch (Exception e){
            throw new DataException(e.getMessage());
        }
    }

    /*=====================超时方案=====================*/

    public static Connection getConnTimeout(DataSourceMod dbSourceOrDbLink) throws DataException {
        return getConn(dbSourceOrDbLink,null);
    }

    public static Connection getConnTimeout(DataSourceMod dataSourceUtil,  String dbName) throws DataException {
        DataSourceDTO dataSourceDTO = dataSourceUtil.dataSourceDTO();
        DbBase dbBase = DbTypeUtil.getDb(dataSourceUtil);
        DataSourceDTO dsd = dataSourceDTO.reDbName(dbName);
        return createConn(dbBase.getDriver(), dataSourceDTO.getUserName(), dataSourceDTO.getPassword(),
                dbBase.getConnUrl(dsd.getPrepareUrl(),dsd.getHost(),dsd.getPort(),dsd.getDbName()),3L,"254");
    }

    public static Connection getConnTimeout(String userName, String password, String url) throws DataException{
        return createConn(DbTypeUtil.getDb(url).getDriver(), userName, password, url, 3L,"254");
    }

    /**
     * （超时方案）获取数据连接 - （防止有些数据库，长时间连接不成功也也不报错的情况）
     * @param userName 用户
     * @param password 密码
     * @param url url
     * @param driverClass 驱动
     * @param timeoutNum 超时时间
     * @param warning 错误类型
     * @param warning 报错编码
     * @throws DataException 连接错误
     */
    private static Connection createConn(String driverClass, String userName, String password, String url, Long timeoutNum, String warning) throws DataException{
        final Connection[] conn = {null};
        Callable<String> task = getTask(userName,password,url,driverClass,conn);
        futureGo(task, warning, timeoutNum);
        try {
            Connection cConn = conn[0];
            return cConn;
        }catch (Exception e){
            throw new DataException(e.getMessage());
        }
    }

    private static void futureGo(Callable<String> task, String warning, Long timeoutNum) throws DataException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Future<String> future = executorService.submit(task);
        try {
            //设置超时时间（默认3L）
            String rst = future.get(timeoutNum, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            throw DataException.errorLink(warning,"连接超时");
        } catch (Exception e) {
            throw DataException.errorLink(warning,"连接错误");
        } finally {
            executorService.shutdown();
        }
    }

    private static Callable<String> getTask(String userName, String password, String url, String driverClass, Connection[] conn)throws DataException {
        Callable<String> task = () -> {
            //执行耗时代码
            try{
                Class.forName(driverClass);
                conn[0] = DriverManager.getConnection(url, userName, password);
            }catch (Exception e){
                e.printStackTrace();
                throw new DataException(e.getMessage());
            }
            return "jdbc连接成功";
        };
        return task;
    }

}
